#include "ecv_vi_ogv.h"


/*
 * Gstreamer includes
 */

#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include <gst/app/gstappbuffer.h>
#include <gst/app/gstappsink.h>

G_DEFINE_TYPE (EcvVi_Ogv, ecv_vi_ogv, ECV_TYPE_VI);

#define ECV_VI_OGV_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ECV_TYPE_VI_OGV, EcvVi_OgvPrivate))

struct _EcvVi_OgvPrivate
{
  gchar * file;
  int width;
  int height;
  int bpp;
  char * data;
  GstBuffer *buf;
  GstElement *demux;
  GstElement *dec;
  GstElement *pipe_pro;
  GstElement *ffcs;
  GstElement *cf2;
  GstElement *sink;
  GstElement *src;
  int failed;
};

enum
{
  PROP_0,
  PROP_ECV_WIDTH,
  PROP_ECV_HEIGHT,
  PROP_ECV_BPP,
  PROP_ECV_FILE
};

//------------------------------------------------------
void 
ogv_run (EcvVi *self)
{
  g_return_if_fail (ECV_IS_VI (self));

  EcvVi_Ogv * ogv = ECV_VI_OGV(self);

  if (gst_element_set_state(GST_ELEMENT(ogv->priv->pipe_pro),GST_STATE_PLAYING)==GST_STATE_CHANGE_FAILURE)
    {
      printf ("Gstreamer failed to create pipeline\n");
      ogv->priv->failed = 1;
    }
}

//------------------------------------------------------
void 
ogv_step_begin (EcvVi *self)
{
  g_return_if_fail (ECV_IS_VI (self));

  EcvVi_Ogv * ogv = ECV_VI_OGV(self);

  if (!gst_app_sink_is_eos (GST_APP_SINK (ogv->priv->sink)))
    {
      ogv->priv->buf = gst_app_sink_pull_buffer (GST_APP_SINK (ogv->priv->sink));
    }
  else 
    ogv->priv->buf = NULL;
}

//------------------------------------------------------

void 
ogv_step_end (EcvVi *self)
{
  g_return_if_fail (ECV_IS_VI (self));
  
  EcvVi_Ogv * ogv = ECV_VI_OGV(self);

  if (ogv->priv->buf != NULL)
    gst_buffer_unref (ogv->priv->buf);
}

//------------------------------------------------------

char * 
ogv_get_data (EcvVi *self)
{
  g_return_if_fail (ECV_IS_VI (self));
  
  EcvVi_Ogv * ogv = ECV_VI_OGV(self);
  
  if (ogv->priv->buf != NULL)
    return (ogv->priv->buf->data);
  return NULL;
}

//------------------------------------------------------

int 
ogv_get_data_size (EcvVi *self)
{
  g_return_if_fail (ECV_IS_VI (self));
  
  EcvVi_Ogv * ogv = ECV_VI_OGV(self);

  if (ogv->priv->buf != NULL)
    return (ogv->priv->buf->size);
  return -1;
}

int
ogv_failed(EcvVi * self)
{
  g_return_if_fail (ECV_IS_VI (self));
  
  EcvVi_Ogv * ogv = ECV_VI_OGV(self);

  return ogv->priv->failed;
}

ogv_create (EcvVi *self)
{
  g_return_if_fail (ECV_IS_VI (self));

  EcvVi_Ogv * ogv = ECV_VI_OGV(self);

  ogv->priv->pipe_pro = gst_pipeline_new (NULL);
  g_assert (ogv->priv->pipe_pro);

  char device[512];
  
  sprintf (device,"filesrc location=%s",ogv->priv->file);
  printf ("gstreamer pipe src: %s\n", device);

  ogv->priv->src = gst_parse_launch(device, NULL);
  g_assert (ogv->priv->src);
  gst_bin_add (GST_BIN (ogv->priv->pipe_pro), ogv->priv->src);

  ogv->priv->demux = gst_element_factory_make ("oggdemux", NULL);
  g_assert (ogv->priv->demux);
  gst_bin_add (GST_BIN (ogv->priv->pipe_pro), ogv->priv->demux);

  ogv->priv->dec = gst_element_factory_make ("theoradec", NULL);
  g_assert (ogv->priv->dec);
  gst_bin_add (GST_BIN (ogv->priv->pipe_pro), ogv->priv->dec);


  /*  if (m_cla->method != ECV_LIGHTS)
    {
      sprintf(device, "capsfilter caps=\"video/x-raw-yuv,width=%i,height=%i\"",m_cla->width,m_cla->height);
      printf("capsfilter caps=\"video/x-raw-yuv,format:fourcc=YV12,width=%i,height=%i\"\n",m_cla->width,m_cla->height);
      app->cf = gst_parse_launch(device, NULL);
      g_assert (app->cf);
      gst_bin_add (GST_BIN (app->pipe_pro), app->cf);
      }*/


  ogv->priv->ffcs = gst_element_factory_make ("ffmpegcolorspace", NULL);
  g_assert (ogv->priv->ffcs);
  gst_bin_add (GST_BIN (ogv->priv->pipe_pro), ogv->priv->ffcs);

  sprintf(device, "capsfilter caps=\"video/x-raw-rgb,width=%i,height=%i,bpp=24,red_mask=255, green_mask=65280, blue_mask=16711680, endianness=4321\"",ogv->priv->width,ogv->priv->height);
  printf("capsfilter caps=\"video/x-raw-rgb,width=%i,height=%i,bpp=(int)24,red_mask=(int)255, green_mask=(int)65280, blue_mask=(int)16711680, endianness=(int)4321\"\n",ogv->priv->width,ogv->priv->height);
  ogv->priv->cf2 = gst_parse_launch(device, NULL);
  g_assert (ogv->priv->cf2);
  gst_bin_add (GST_BIN (ogv->priv->pipe_pro), ogv->priv->cf2);

  ogv->priv->sink = gst_element_factory_make ("appsink", NULL);
  g_assert (ogv->priv->sink);

  //  gst_app_sink_set_max_buffers(ogv->priv->sink, 1);
  //  gst_app_sink_set_drop(ogv->priv->sink, TRUE);
  gst_bin_add (GST_BIN (ogv->priv->pipe_pro), ogv->priv->sink);


  gst_element_link (ogv->priv->src, ogv->priv->demux);
  gst_element_link (ogv->priv->demux, ogv->priv->dec);
  //  gst_element_link (ogv->priv->dec, ogv->priv->ffcs);
  //  gst_element_link (ogv->priv->ffcs, ogv->priv->cf2);
  //  gst_element_link (ogv->priv->cf2, ogv->priv->sink);
  gst_element_link (ogv->priv->dec, ogv->priv->sink);
}

static void
ecv_vi_ogv_set_property (GObject      *object,
                        guint         property_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
  EcvVi_Ogv *self = ECV_VI_OGV (object);

  switch (property_id)
    {
    case PROP_ECV_WIDTH:
      self->priv->width = g_value_get_uint (value);
      break;

    case PROP_ECV_HEIGHT:
      self->priv->height = g_value_get_uint (value);
      break;

    case PROP_ECV_BPP:
      self->priv->bpp = g_value_get_uint (value);
      break;

    case PROP_ECV_FILE:
      self->priv->file = g_strdup(g_value_get_string (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
ecv_vi_ogv_get_property (GObject    *object,
                        guint       property_id,
                        GValue     *value,
                        GParamSpec *pspec)
{
  EcvVi_Ogv *self = ECV_VI_OGV (object);

  switch (property_id)
    {
    case PROP_ECV_WIDTH:
      g_value_set_uint (value, self->priv->width);
      break;

    case PROP_ECV_HEIGHT:
      g_value_set_uint (value, self->priv->height);
      break;

    case PROP_ECV_BPP:
      g_value_set_uint (value, self->priv->bpp);
      break;

    case PROP_ECV_FILE:
      g_value_set_string (value, self->priv->file);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
      }
}

static void
ecv_vi_ogv_dispose (GObject *gobject)
{
  EcvVi_Ogv *self = ECV_VI_OGV (gobject);

  /* 
   * In dispose, you are supposed to free all types referenced from this
   * object which might themselves hold a reference to self. Generally,
   * the most simple solution is to unref all members on which you own a 
   * reference.
   */

  /* dispose might be called multiple times, so we must guard against
   * calling g_object_unref() on an invalid GObject.
   */

  /*  if (self->priv->)
    {
      g_object_unref (self->priv->an_object);
      }*/

  /* Chain up to the parent class */
  G_OBJECT_CLASS (ecv_vi_ogv_parent_class)->dispose (gobject);
}

static void
ecv_vi_ogv_finalize (GObject *gobject)
{
  EcvVi_Ogv *self = ECV_VI_OGV (gobject);

  if (self->priv->data != NULL)
    {
      free(self->priv->data);
      self->priv->data = NULL;
    }

  //  g_free (self->a_string);

  /* Chain up to the parent class */
  G_OBJECT_CLASS (ecv_vi_ogv_parent_class)->finalize (gobject);
}

static GObject *
ecv_vi_ogv_constructor (GType                  gtype,
                       guint                  n_properties,
                       GObjectConstructParam *properties)
{
  GObject *obj;

  {
    /* Always chain up to the parent constructor */
    EcvVi_OgvClass *klass;
    GObjectClass *parent_class;  
    parent_class = G_OBJECT_CLASS (ecv_vi_ogv_parent_class);
    obj = parent_class->constructor (gtype, n_properties, properties);
  }
  
  /* update the object state depending on constructor properties */

  return obj;
}

static void
ecv_vi_ogv_class_init (EcvVi_OgvClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  GParamSpec *pspec;

  gobject_class->dispose = ecv_vi_ogv_dispose;
  gobject_class->finalize = ecv_vi_ogv_finalize;
  gobject_class->set_property = ecv_vi_ogv_set_property;
  gobject_class->get_property = ecv_vi_ogv_get_property;
pspec = g_param_spec_uint ("width",
			     "",
			     "image width",
			     1,
			     65534,
			     320,
			     G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
  
  g_object_class_install_property (gobject_class,
                                   PROP_ECV_WIDTH,
                                   pspec);

  pspec = g_param_spec_uint ("height",
			     "",
			     "image height",
			     1,
			     65534,
			     240,
			     G_PARAM_READWRITE);
  g_object_class_install_property (gobject_class,
                                   PROP_ECV_HEIGHT,
                                   pspec);

  pspec = g_param_spec_uint ("bpp",
			     "",
			     "image bytes per pixel",
			     1,
			     65534,
			     24,
			     G_PARAM_READWRITE);
  g_object_class_install_property (gobject_class,
                                   PROP_ECV_BPP,
                                   pspec);

  pspec = g_param_spec_string ("file",
			       "",
			       "file name",
			       "",
			       G_PARAM_READWRITE);
  g_object_class_install_property (gobject_class,
                                   PROP_ECV_FILE,
                                   pspec);

  g_type_class_add_private (klass, sizeof (EcvVi_OgvPrivate));
  gobject_class->constructor = ecv_vi_ogv_constructor;

  EcvViClass *parent_class;  
  parent_class = ECV_VI_CLASS (klass);
  parent_class->create        = ogv_create;
  parent_class->run           = ogv_run;
  parent_class->step_begin    = ogv_step_begin;
  parent_class->step_end      = ogv_step_end;
  parent_class->get_data      = ogv_get_data;
  parent_class->get_data_size = ogv_get_data_size;
  parent_class->failed        = ogv_failed;
}

static void
ecv_vi_ogv_init (EcvVi_Ogv *self)
{
  self->priv = ECV_VI_OGV_GET_PRIVATE (self);
  self->priv->failed = 0;

  //  self->priv->an_object = g_object_new (ECV_TYPE_BA, NULL);
}

//------------------------------------------------------
/*                     methods                        */
//------------------------------------------------------

EcvVi_Ogv * 
ecv_vi_ogv_new (int width, int height, int bpp, char * filename)
{
  return g_object_new (ECV_TYPE_VI_OGV,
		       "width", width,
		       "height", height,
		       "bpp", bpp,
		       "file", filename,
		       NULL);
}
